/*	--- 装配AOP --- 

在【AOP编程】中，我们经常会遇到下面的【概念】：

	· Aspect：切面，即一个【横跨多个核心逻辑】的【功能】，或者称之为【系统关注点】;
	· Joinpoint：连接点，即定义在【应用程序流程】的【何处插入切面】的【执行】;
	· Pointcut：切入点，即【一组连接点的集合】;
	· Advice：增强，指【特定连接点上】, 执行的【动作】;
	· Introduction：引介，指为一个【已有的Java对象】动态地增加【新的接口】;
	· Weaving：织入，指将【切面】整合到【程序的执行流程】中;
	· Interceptor：拦截器，是一种【实现增强的方式】;
	· Target Object：目标对象，即真正执行【业务的核心逻辑对象】;
	· AOP Proxy：AOP代理，是【客户端】持有的【增强后的对象引用】。


看完【上述术语】，是不是感觉对【AOP】有了【进一步的困惑】？

其实，我们不用关心【AOP】创造的“术语”，只需要理解【AOP本质】上只是一种【代理模式】的实现方式

在【Spring的容器】中, 实现【AOP】特别方便。

我们以【UserService 和 MailService】为例，这【两个】属于【核心业务逻辑】，

现在，我们准备给【UserService的每个业务方法】执行前【添加日志】，给【MailService的每个业务方法】执行前后【添加日志】

------------------------------------

在【Spring】中，需要【以下步骤】：

1. 首先，我们通过【Maven】引入【Spring对AOP的支持】：

	<dependency>
	    <groupId>org.springframework</groupId>
	    <artifactId>spring-aspects</artifactId>   <---- sprint-aspects
	    <version>${spring.version}</version>
	</dependency>


【上述依赖】会自动引入【AspectJ】，使用【AspectJ】实现【AOP】比较方便，因为【它的定义】比较简单。

然后，我们定义【一个LoggingAspect】： */
@Aspect 
@Component
public class LoggingAspect {
	// 在执行UserService的每个"方法前"执行
	@Before("execution(public * com.itranswarp.learnjava.service.UserService.*(..))")
	public void doAccessCheck() {
		System.err.println("[Before] do access check...");
	}

	// 在执行MailService的每个"方法前后"执行
	@Around("execution(public * com.itranswarp.learnjava.service.MailService.*(..))")
	public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {
		System.err.println("[Around] start " + pjp.getSignature());

		Object retVal = pjp.proceed();
		System.out.println("[Around] done " + pjp.getSignature());

		return retVal;
	}
}


/*
观察【doAccessCheck()方法】，我们定义了一个【@Before注解】，【后面的字符串】是告诉【AspectJ】应该【在何处】执行【该方法】

@Before("execution(public * com.itranswarp.learnjava.service.UserService.*(..))") 写的意思是：
	执行【UserService的每个public方法】前, 执行【doAccessCheck()代码】。

再观察【doLogging()方法】，我们定义了一个【@Around注解】，它和【@Before】不同，【@Around】可以决定【是否执行目标方法】，因此，我们在【doLogging()】内部先【打印日志】，再【调用方法】，最后【打印日志】后, 返回结果。

-------------------------------------

在LoggingAspect类的声明处，除了用@Component表示它本身也是一个Bean外，我们再加上【@Aspect注解】，表示它的【@Before标注的方法】需要注入到【UserService的每个public方法"执行前"】，【@Around标注的方法】需要注入到【MailService的每个public方法"执行前后"】。

紧接着，我们需要给【@Configuration类】加上【一个@EnableAspectJAutoProxy注解】： */
@Configuration 
@ComponentScan
@EnableAspectJAutoProxy 
public class AppConfig {
	//...
}


/*
【Spring的IoC容器】看到这个【注解】，就会自动查找【带有@Aspect的Bean】，然后根据【每个方法的@Before、@Around等注解】把【AOP】注入到【特定的Bean】中。

执行代码，我们可以看到以下输出：
---------------------------------------------------
[Before] do access check...
[Around] start void com.itranswarp.learnjava.service.MailService.sendRegistrationMail(User)
Welcome, test!
[Around] done void com.itranswarp.learnjava.service.MailService.sendRegistrationMail(User)
[Before] do access check...
[Around] start void com.itranswarp.learnjava.service.MailService.sendLoginMail(User)
Hi, Bob! You are logged in at 2020-02-14T23:13:52.167996+08:00[Asia/Shanghai]
[Around] done void com.itranswarp.learnjava.service.MailService.sendLoginMail(User)

----------------------------------------------------


这说明执行【业务逻辑前后】，确实执行了【我们定义的Aspect】（即LoggingAspect的方法）。

有些童鞋会问，【LoggingAspect定义的方法】，是如何注入到【其他Bean】的呢？

其实【AOP的原理】非常简单。

我们以 LoggingAspect.doAccessCheck() 为例，要把它注入到【UserService的每个public方法】中，最简单的方法是【编写一个子类】，并持有【原始实例的引用】： */
public UserServiceAopProxy extends UserService {

	private UserService target;
	private LoggingAspect aspect;

	public UserServiceAopProxy(UserService target, LoggingAspect aspect) {
		this.target = target;
		this.aspect = aspect;
	}

	public User login(String email, String password) {
		// 先执行Aspect的代码
		aspect.doAccessCheck();

		// 再执行UserService的逻辑
		return target.login(email, password);
	}

	public User register(String email, String password, String name) {
		aspect.doAccessCheck();

		return target.register(email, password, name);
	}
}


/*
这些都是【Spring容器】启动时, 为我们【自动创建】的注入了【Aspect的子类】，它取代了【原始的UserService】
（原始的【UserService实例】作为【内部变量】隐藏在【UserServiceAopProxy】中）。

如果我们打印从【Spring容器】获取的【UserService实例类型】，它类似
	UserService$$EnhancerBySpringCGLIB$$1f44e01c

实际上是【Spring】使用【CGLIB】动态创建的【子类】，但对于【调用方】来说，感觉不到任何区别。

#	PS: 【Spring】对【接口类型 Interface】使用【JDK动态代理】，对【普通类】使用【CGLIB创建子类】。 如果【一个Bean的class】是【final】，【Spring】将【无法为其创建子类】。

----------------------------------------


可见，虽然【Spring容器】内部实现【AOP的逻辑】比较复杂
（需要【使用AspectJ】解析【注解】，并通过【CGLIB】实现【代理类】），但我们【使用AOP】非常简单，一共需要【3步】：

1. 定义【执行方法】，并在【方法上】通过【AspectJ的注解】告诉【Spring】应该【在何处】调用【此方法】;
2. 标记【 @Component 和 @Aspect 】;
3. 在【@Configuration类】上, 标注【@EnableAspectJAutoProxy】。


至于AspectJ的注入语法则比较复杂，请参考Spring文档。

Spring也提供其他方法来装配AOP，但都没有使用AspectJ注解的方式来得简洁明了，所以我们不再作介绍。


=====================================================================


#	拦截器类型

顾名思义，【拦截器】有以下类型：

@Before：
 这种【拦截器】先执行【拦截代码】，再执行【目标代码】。如果【拦截器】抛异常，那么【目标代码】就不执行了;

@After： 
 这种【拦截器】先执行【目标代码】，再执行【拦截器代码】。无论【目标代码】是否【抛异常】，拦截器代码【都会执行】;

@AfterReturning： 
 和@After不同的是，只有当【目标代码 正常返回】时，才执行拦截器代码;

@AfterThrowing： 
 和@After不同的是，只有当【目标代码 抛出了异常】时，才执行拦截器代码;

@Around：
 能完全控制目标代码是否执行，并可以在执行前后、抛异常后【执行任意拦截代码】，可以说是【包含了上面所有功能】。



======================================================================


#	练习 

使用AOP实现日志 */


/*=======================================================================

#	----- 装配AOP の 小结 ----- 


1. 在【Spring容器】中, 【使用AOP】非常简单，只需要【定义执行方法】，并用【AspectJ的注解】标注【应该在何处】触发并执行。

2. 【Spring】通过【CGLIB】动态创建【子类等】方式, 来实现【AOP代理模式】，大大简化了代码。
	CGLIB 对 原有的class 进行 代理封装

打印从【Spring容器】获取的【UserService实例类型(被代理后的子类)】，它类似
	UserService$$EnhancerBySpringCGLIB$$1f44e01c

---------------------------

代码模板:  */
@Aspect  // 表示@Before @Around 需要拦截(...)括号中的class.Method()调用,并执行拦截代码
@Component
public class LoggingAspect {
	// 在执行UserService的每个"public 方法 前"执行
	@Before("execution(public * com.itranswarp.learnjava.service.UserService.*(..))")
	public void doAccessCheck() {
		System.err.println("[Before] do access check...");
	}

	// 在执行MailService的每个"public 方法前后"执行
	@Around("execution(public * com.itranswarp.learnjava.service.MailService.*(..))")
	public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {
		System.err.println("[Around] start " + pjp.getSignature());

		Object retVal = pjp.proceed();
		System.out.println("[Around] done " + pjp.getSignature());

		return retVal;
	}
}


// 还需要给 @Configuration类 加上一个 @EnableAspectJAutoProxy注解
@Configuration
@ComponentScan
@EnableAspectJAutoProxy // 自动查找【所有带有@Aspect的Bean】, 并根据拦截条件进行对【配置中的各个class】进行Aspect
public class AppConfig {
    ...
}























